package com.ss.android.ugc.bytex.common;

import com.android.annotations.NonNull;
import com.android.build.api.transform.QualifiedContent;
import com.android.build.api.transform.SecondaryFile;
import com.android.build.gradle.internal.pipeline.TransformManager;
import com.android.build.gradle.internal.scope.VariantScope;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.ss.android.ugc.bytex.common.utils.ReflectionUtils;

import java.io.File;
import java.util.Collection;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nullable;

public interface TransformConfiguration {

    TransformConfiguration DEFAULT = new TransformConfiguration() {
    };

    /**
     * Returns the type(s) of data that is consumed by the Transform. This may be more than
     * one type.
     *
     * <strong>This must be of type {@link QualifiedContent.DefaultContentType}</strong>
     */
    @NonNull
    default Set<QualifiedContent.ContentType> getInputTypes() {
        return TransformManager.CONTENT_CLASS;
    }

    /**
     * Returns the type(s) of data that is generated by the Transform. This may be more than
     * one type.
     *
     * <p>The default implementation returns {@link #getInputTypes()}.
     *
     * <p><strong>This must be of type {@link QualifiedContent.DefaultContentType}</strong>
     */
    @NonNull
    default Set<QualifiedContent.ContentType> getOutputTypes() {
        return getInputTypes();
    }

    /**
     * Returns the scope(s) of the Transform. This indicates which scopes the handle consumes.
     */
    @NonNull
    default Set<? super QualifiedContent.Scope> getScopes(@Nullable VariantScope variantScope) {
        try {
            if (variantScope == null || this.getClass().getMethod("getScopes").getDeclaringClass() != TransformConfiguration.class) {
                return getScopes();
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        if (variantScope.consumesFeatureJars() && consumesFeatureJars()) {
            return TransformManager.SCOPE_FULL_WITH_FEATURES;
        } else {
            return TransformManager.SCOPE_FULL_PROJECT;
        }
    }

    /**
     * Returns the scope(s) of the Transform. This indicates which scopes the handle consumes.
     */
    @NonNull
    @Deprecated
    default Set<? super QualifiedContent.Scope> getScopes() {
        if (consumesFeatureJars()) {
            try {
                return ReflectionUtils.getField(TransformManager.class, TransformManager.class, "SCOPE_FULL_WITH_FEATURES");
            } catch (Exception ignored) {
            }
        }
        // 需要gradle 3.2 开始支持
        // return TransformManager.SCOPE_FULL_WITH_FEATURES
        return TransformManager.SCOPE_FULL_PROJECT;
    }


    /**
     * Returns the referenced scope(s) for the Transform. These scopes are not consumed by
     * the Transform. They are provided as inputs, but are still available as inputs for
     * other Transforms to consume.
     *
     * <p>The default implementation returns an empty Set.
     */
    @NonNull
    default Set<? super QualifiedContent.Scope> getReferencedScopes(@Nullable VariantScope variantScope) {
        try {
            if (variantScope == null || this.getClass().getMethod("getReferencedScopes").getDeclaringClass() != TransformConfiguration.class) {
                return getReferencedScopes();
            }
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
        return ImmutableSet.of();
    }

    /**
     * Returns the referenced scope(s) for the Transform. These scopes are not consumed by
     * the Transform. They are provided as inputs, but are still available as inputs for
     * other Transforms to consume.
     *
     * <p>The default implementation returns an empty Set.
     */
    @NonNull
    @Deprecated
    default Set<? super QualifiedContent.Scope> getReferencedScopes() {
        return ImmutableSet.of();
    }

    /**
     * Returns a list of additional file(s) that this Transform needs to run. Preferably, use
     * {@link #getSecondaryFiles()} API which allow eah secondary file to indicate if changes
     * can be handled incrementally or not. This API will treat all additional file change as
     * a non incremental event.
     *
     * <p>Changes to files returned in this list will trigger a new execution of the Transform
     * even if the qualified-content inputs haven't been touched.
     *
     * <p>Any changes to these files will trigger a non incremental execution.
     *
     * <p>The default implementation returns an empty collection.
     *
     * @deprecated replaced by {@link #getSecondaryFiles()}
     */
    @Deprecated
    @NonNull
    default Collection<File> getSecondaryFileInputs() {
        return ImmutableList.of();
    }

    /**
     * Returns a list of additional file(s) that this Transform needs to run.
     *
     * <p>Changes to files returned in this list will trigger a new execution of the Transform
     * even if the qualified-content inputs haven't been touched.
     *
     * <p>Each secondary input has the ability to be declared as necessitating a non incremental
     * execution in case of change. This Transform can therefore declare which secondary file
     * changes it supports in incremental mode.
     *
     * <p>The default implementation returns an empty collection.
     */
    @NonNull
    default Collection<SecondaryFile> getSecondaryFiles() {
        return ImmutableList.of();
    }

    /**
     * Returns a list of additional (out of streams) file(s) that this Transform creates.
     *
     * <p>These File instances can only represent files, not directories. For directories, use
     * {@link #getSecondaryDirectoryOutputs()}
     *
     *
     * <p>Changes to files returned in this list will trigger a new execution of the Transform
     * even if the qualified-content inputs haven't been touched.
     *
     * <p>Changes to these output files force a non incremental execution.
     *
     * <p>The default implementation returns an empty collection.
     */
    @NonNull
    default Collection<File> getSecondaryFileOutputs() {
        return ImmutableList.of();
    }

    /**
     * Returns a list of additional (out of streams) directory(ies) that this Transform creates.
     *
     * <p>These File instances can only represent directories. For files, use
     * {@link #getSecondaryFileOutputs()}
     *
     * <p>Changes to directories returned in this list will trigger a new execution of the Transform
     * even if the qualified-content inputs haven't been touched.
     *
     * <p>Changes to these output directories force a non incremental execution.
     *
     * <p>The default implementation returns an empty collection.
     */
    @NonNull
    default Collection<File> getSecondaryDirectoryOutputs() {
        return ImmutableList.of();
    }

    /**
     * Returns a map of non-file input parameters using a unique identifier as the map key.
     *
     * <p>Changes to values returned in this map will trigger a new execution of the Transform
     * even if the content inputs haven't been touched.
     *
     * <p>Changes to these values force a non incremental execution.
     *
     * <p>The default implementation returns an empty Map.
     */
    @NonNull
    default Map<String, Object> getParameterInputs() {
        return ImmutableMap.of();
    }

    default boolean isIncremental() {
        return true;
    }

    default boolean consumesFeatureJars() {
        return true;
    }

}
